// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Diagnostics;

namespace Microsoft.TeamFoundation.Migration.Toolkit.VC
{
    public class SqlChangeGrouping : ChangeGrouping
    {
        public SqlChangeGrouping(ChangeGroupingMananger manager)
            : base(manager)
        {
        }

        public override IMigrationAction CreateAction()
        {
            DemandUnlocked("Cannot Create an action on a grouping after the initial Create");

            return new SqlMigrationAction(this);
        }

        public static int PromoteAnalysisToPending(string sessionId, MigrationDirection direction)
        {
            int rows = -1;

            using (IMigrationTransaction trx = DataAccessManager.Current.StartTransaction())
            {
                rows = PromoteAnalysisToPending(trx, sessionId, direction);
                trx.Complete();
            }

            return rows;
        }

        public static int PromoteAnalysisToPending(IMigrationTransaction trx, string sessionId, MigrationDirection direction)
        {
            MigrationSqlTransaction sqlTrx = (MigrationSqlTransaction)trx;

            int rows = -1;

            using (SqlCommand cmd = sqlTrx.CreateCommand())
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_iiPromoteAnalysisToPending";

                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = sessionId;
                cmd.Parameters.Add("@Direction", SqlDbType.Int).Value = direction;

                rows = DataAccessManager.ExecuteNonQuery(cmd);
            }

            return rows;

        }

        public static int DemoteInProgressActionsToPending(string sessionId, MigrationDirection direction)
        {
            int rows = -1;

            using (IMigrationTransaction trx = DataAccessManager.Current.StartTransaction())
            {
                rows = DemoteInProgressActionsToPending(trx, sessionId, direction);
                trx.Complete();
            }

            return rows;
        }


        public static int DemoteInProgressActionsToPending(IMigrationTransaction trx, string sessionId, MigrationDirection direction)
        {
            MigrationSqlTransaction sqlTrx = (MigrationSqlTransaction)trx;

            int rows = -1;

            using (SqlCommand cmd = sqlTrx.CreateCommand())
            {
                cmd.CommandTimeout = 0;
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_iiDemoteInProgressActionsToPending";

                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = sessionId;
                cmd.Parameters.Add("@Direction", SqlDbType.Int).Value = direction;

                rows = DataAccessManager.ExecuteNonQuery(cmd);
            }

            return rows;
        }

        protected override void Create(IMigrationTransaction trx)
        {
            Debug.Assert(!Locked);

            MigrationSqlTransaction sqlTrx = (MigrationSqlTransaction)trx;

            using (SqlCommand cmd = sqlTrx.CreateCommand())
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_iiCreateChangeGrouping";

                cmd.Parameters.Add("@ExecutionOrder", SqlDbType.BigInt).Value = ExecutionOrder;
                cmd.Parameters.Add("@Owner", SqlDbType.NVarChar).Value = getSqlSafeString(Owner);
                cmd.Parameters.Add("@Comment", SqlDbType.NVarChar).Value = getSqlSafeString(Comment);
                cmd.Parameters.Add("@ChangeTime", SqlDbType.DateTime).Value = ChangeTimeUtc;
                cmd.Parameters.Add("@Status", SqlDbType.Int).Value = Status;
                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = getSqlSafeString(SessionId);
                cmd.Parameters.Add("@Direction", SqlDbType.Int).Value = Direction;
                cmd.Parameters.Add("@Name", SqlDbType.NVarChar).Value = getSqlSafeString(Name);

                ChangeGrouping temp;

                temp = DataAccessManager.ExecuteReader<ChangeGrouping>(
                    cmd,
                    delegate(SqlDataReader reader)
                    {
                        return realizeChangeGrouping(reader, Manager);
                    });

                ChangeGroupId = temp.ChangeGroupId;
                ChangeTimeUtc = temp.ChangeTimeUtc;
                ExecutionOrder = temp.ExecutionOrder;

                foreach (MigrationAction action in Actions)
                {
                    action.ChangeGroup = this;
                    action.CreateNew(trx);
                }

                Locked = true;
            }
        }

        private static object getSqlSafeString(string str)
        {
            return (str == null) ? string.Empty : (object)str;
        }

        protected override void Update(IMigrationTransaction trx)
        {
            MigrationSqlTransaction sqlTrx = (MigrationSqlTransaction)trx;

            using (SqlCommand cmd = sqlTrx.CreateCommand())
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "prc_iiUpdateChangeGroupingStatus";

                cmd.Parameters.Add("@ChangeGroupID", SqlDbType.Int).Value = ChangeGroupId;
                cmd.Parameters.Add("@Status", SqlDbType.Int).Value = Status;

                int rows = DataAccessManager.ExecuteNonQuery(cmd);

                if (rows != 1)
                {
                    throw new MigrationException(
                        string.Format(
                        MigrationToolkitVCResources.Culture,
                        MigrationToolkitVCResources.WrongRowCountUpdatingChangeGrouping, 
                        rows)
                    );
                }
            }
        }

        internal static ChangeGrouping Next(ChangeGroupingMananger manager)
        {
            SqlChangeGrouping grouping = null;

            using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
            {
                conn.Open();

                using (SqlCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.CommandText = "prc_QueryNextChangeGrouping";
                    cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = manager.Session.Id;
                    cmd.Parameters.Add("@Direction", SqlDbType.Int).Value = (int)manager.Direction;

                    grouping = (SqlChangeGrouping)DataAccessManager.ExecuteReader<ChangeGrouping>(
                        cmd,
                        delegate(SqlDataReader reader)
                        {
                            return realizeChangeGrouping(reader, manager);
                        });
                }

                if (grouping != null)
                {
                    using (SqlCommand cmd = conn.CreateCommand())
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.CommandText = "prc_QueryActionData";

                        cmd.Parameters.Add("@ChangeGroupID", SqlDbType.Int).Value = grouping.ChangeGroupId;

                        grouping.Actions = DataAccessManager.ExecuteReader<Collection<IMigrationAction>>(
                            cmd,
                            delegate(SqlDataReader reader)
                            {
                                return realizeActionList(reader, manager, grouping);
                            });
                    }

                    foreach (MigrationAction ma in grouping.Actions)
                    {
                        ma.ChangeGroup = grouping;
                    }
                }
            }

            return grouping;
        }

        private static Collection<IMigrationAction> realizeActionList(SqlDataReader reader, ChangeGroupingMananger manager, ChangeGrouping parent)
        {
            Collection<IMigrationAction> actions = new Collection<IMigrationAction>();

            if (reader != null && reader.HasRows)
            {
                while (reader.Read())
                {
                    loadActionOrdinalCache(reader);

#if DEBUG
                    int changeGroupId = reader.GetInt32(s_ordinalActionChangeGroupID);
                    Debug.Assert(changeGroupId == parent.ChangeGroupId);
#endif

                    MigrationAction action = new SqlMigrationAction(parent, reader.GetInt32(s_ordinalActionActionId));
                    action.setStateWithoutUpdatingDb((ActionState)reader.GetInt32(s_ordinalActionState));
                    action.Order = reader.GetInt32(s_ordinalActionOrder);

                    action.Action = (ChangeAction)reader.GetInt32(s_ordinalActionAction);

                    action.Recursive = reader.GetBoolean(s_ordinalActionRecursive);

                    if (!reader.IsDBNull(s_ordinalActionSourceItem))
                    {
                        action.SourceItem = manager.SourceSerializer.LoadItem(reader.GetString(s_ordinalActionSourceItem), manager);
                    }

                    if (!reader.IsDBNull(s_ordinalActionTargetSourceItem))
                    {
                        action.TargetSourceItem = manager.TargetSerializer.LoadItem(reader.GetString(s_ordinalActionTargetSourceItem), manager);
                    }

                    // s_ordinalActionTargetTargetItemName should never be null
                    action.TargetTargetItem = manager.TargetSerializer.LoadItem(reader.GetString(s_ordinalActionTargetTargetItem), manager);

                    if (!reader.IsDBNull(s_ordinalActionLabel))
                    {
                        action.Label = reader.GetString(s_ordinalActionLabel);
                    }

                    if (!reader.IsDBNull(s_ordinalActionVersion))
                    {
                        action.Version = reader.GetString(s_ordinalActionVersion);
                    }

                    if (!reader.IsDBNull(s_ordianlActionMergeVersionTo))
                    {
                        action.MergeVersionTo = reader.GetString(s_ordianlActionMergeVersionTo);
                    }


                    actions.Add(action);
                }
            }

            return actions;
        }

        private static void loadActionOrdinalCache(SqlDataReader reader)
        {
            if (!s_ordinalActionCacheLoaded)
            {
                lock (s_ordinalCacheLocker)
                {
                    if (!s_ordinalActionCacheLoaded)
                    {
                        Debug.Assert(reader.FieldCount == 12);
                        s_ordinalActionChangeGroupID = reader.GetOrdinal("ChangeGroupID");
                        s_ordinalActionActionId = reader.GetOrdinal("ActionId");
                        s_ordinalActionOrder = reader.GetOrdinal("Order");
                        s_ordinalActionState = reader.GetOrdinal("State");
                        s_ordinalActionAction = reader.GetOrdinal("BasicAction");
                        s_ordinalActionRecursive = reader.GetOrdinal("Recursivity");
                        s_ordinalActionSourceItem = reader.GetOrdinal("SourceItem");
                        s_ordinalActionTargetSourceItem = reader.GetOrdinal("TargetSourceItem");
                        s_ordinalActionTargetTargetItem = reader.GetOrdinal("TargetTargetItem");
                        s_ordinalActionLabel = reader.GetOrdinal("Label");
                        s_ordinalActionVersion = reader.GetOrdinal("Version");
                        s_ordianlActionMergeVersionTo = reader.GetOrdinal("MergeVersionTo");

                        s_ordinalActionCacheLoaded = true;
                    }
                }
            }
        }


        private static ChangeGrouping realizeChangeGrouping(SqlDataReader reader, ChangeGroupingMananger manager)
        {
            SqlChangeGrouping grouping = null;

            if (reader != null && reader.HasRows)
            {
                if (reader.Read())
                {
                    loadOrdinalCache(reader);

                    grouping = new SqlChangeGrouping(manager);
                    grouping.ChangeGroupId = reader.GetInt32(s_ordinalChangeGroupId);
                    grouping.ChangeTimeUtc = reader.GetDateTime(s_ordinalChangeTime);
                    grouping.Comment = reader.GetString(s_ordinalComment);
                    grouping.ExecutionOrder = reader.GetInt64(s_ordinalExecutionOrder);
                    grouping.Owner = reader.GetString(s_ordinalOwner);
                    grouping.Status = (ChangeStatus)reader.GetInt32(s_ordinalStatus);
                    grouping.SessionId = reader.GetString(s_ordinalSessionId);
                    grouping.Direction = (MigrationDirection)reader.GetInt32(s_ordinalDirection);
                    grouping.Name = reader.GetString(s_ordinalName);
                }

                if (reader.Read())
                {
                    throw new MigrationException(MigrationToolkitVCResources.TooManyChangeGroupsReturned);
                }
            }

            return grouping;
        }

        static void loadOrdinalCache(SqlDataReader reader)
        {
            if (!s_ordinalCacheLoaded)
            {
                lock (s_ordinalCacheLocker)
                {
                    if (!s_ordinalCacheLoaded)
                    {
                        Debug.Assert(reader.FieldCount == 9);
                        s_ordinalChangeGroupId = reader.GetOrdinal("ChangeGroupID");
                        s_ordinalExecutionOrder = reader.GetOrdinal("ExecutionOrder");
                        s_ordinalOwner = reader.GetOrdinal("Owner");
                        s_ordinalComment = reader.GetOrdinal("Comment");
                        s_ordinalChangeTime = reader.GetOrdinal("ChangeTime");
                        s_ordinalStatus = reader.GetOrdinal("Status");
                        s_ordinalSessionId = reader.GetOrdinal("SessionId");
                        s_ordinalDirection = reader.GetOrdinal("Direction");
                        s_ordinalName = reader.GetOrdinal("Name");
                        s_ordinalCacheLoaded = true;
                    }
                }
            }
        }

        static object s_ordinalCacheLocker = new object();
        static bool s_ordinalCacheLoaded;
        static bool s_ordinalActionCacheLoaded;

        static int s_ordinalChangeGroupId;
        static int s_ordinalExecutionOrder;
        static int s_ordinalOwner;
        static int s_ordinalComment;
        static int s_ordinalChangeTime;
        static int s_ordinalStatus;
        static int s_ordinalSessionId;
        static int s_ordinalDirection;
        static int s_ordinalName;

        static int s_ordinalActionChangeGroupID;
        static int s_ordinalActionAction;
        static int s_ordinalActionActionId;
        static int s_ordinalActionState;
        static int s_ordinalActionOrder;
        static int s_ordinalActionRecursive;
        static int s_ordinalActionSourceItem;
        static int s_ordinalActionTargetSourceItem;
        static int s_ordinalActionTargetTargetItem;
        static int s_ordinalActionLabel;
        static int s_ordinalActionVersion;
        static int s_ordianlActionMergeVersionTo;

    }

}
